# 这是一个示例 Python 脚本。
import logging
import threading
import time


# 按 Shift+F10 执行或将其替换为您的代码。
# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。


def print_hi(name):
    # 在下面的代码行中使用断点来调试脚本。
    print(f'Hi, {name}')  # 按 Ctrl+F8 切换断点。

# for 循环取出字符串中的字符
str = 'hello world'
for i in str: # i是临时变量，循环过程中每次迭代都会将字符串中的字符赋值给i
    print(i)

# range(start,stop,step)  step 步长，默认为1
# 用来记录循环的次数，相当于一个计数器
for i in range(1,6): # i从1开始，到5结束，每次加1,包前不包后 1<=i<6
    print(i)


#字符串编码
#本质是二进制与语言文字的一一对应关系
#unicode 所有字符都是2个字节，字符与数字之间转换快，浪费空间
#utf-8 精准,对不同的字符串用不同的长度表示，节省空间，但是速度慢
a = 'hello'
print(a,type(a)) # str,字符串是以字符为单位进行处理的
a1 = a.encode()  # 编码
print("编码后:",a1) #bytes,以字节为单位进行处理的
a2 = a1.decode() #解码
print("解码后:",a2,type(a2))
# 注意：对于bytes，只需要直到它跟字符串类型之间的互相转换
st = "这是测试编码"
st1 = st.encode("utf-8")
print(st1,type(st1))
st2 = st1.decode("utf-8")
print(st2,type(st2))


#字符串操作
# 字符串的拼接，使用+号
name1 = "10"
name2 = "10"
print(name1+name2)

# * 重复输出
print("重复输出\n"*4)

# 成员运算符  检查字符是否在字符串中
# in 包含返回 true 不包含返回 false
# not in 不包含返回 true
name = "hello world"
print('a' in name)
print('a' not in name)
print('hell' in name)

# 下标
# Python中下标从0开始，-1表示最后一个字符，-2表示倒数第二个字符，以此类推
print(name[0])
print(name[-1])

# 切片
# 切片是字符串中一段连续的字符
# 切片语法：[start:end:step] 包前不包后，end不包含
# start 起始位置，默认为0
# end 结束位置，默认为字符串长度
# step 步长，默认为1 正数从左往走，负数从右往左
# 切片是字符串中一段连续的字符，所以切片的结果也是字符串
#从左往右
print(name[0:5])
print(name[0:])
print(name[:5])
print(name[0:5:2])
print(name[::2])
print(name[::-1])
#从右往右
print(name[-1:])

#查找元素
# find：检查某一个子字符串在字符串中的位置，返回第一个匹配的位置，如果没有匹配的返回-1
#find(子字符串,开始位置,结束位置)
name = "hello world"
print(name.find("lo"))
print(name.find("lo",0,5))

# index：检查某一个子字符串在字符串中的位置，返回第一个匹配的位置，如果没有匹配的返回报错
# index(子字符串,开始位置,结束位置)
print(name.index("lo"))
print(name.index("lo",0,5))

#count 计算子字符串出现的次数
#count(子字符串,开始位置,结束位置)
print(name.count("l"))
print(name.count("l",0,5))
print(name.count("l",1))

# startwith 检查字符串是否以指定字符串开头
# startwith(子字符串,开始位置,结束位置)
print(name.startswith("he"))
print(name.startswith("he",0,5))

# endwith 检查字符串是否以指定字符串结尾
# endwith(子字符串,开始位置,结束位置)
print(name.endswith("ld"))
print(name.endswith("ld",0,5))

#split 切割字符串，返回一个列表
#split(分隔符,分割次数) 默认为空格 默认次数为所有
print(name.split("a",1))

#upper 将字符串中的字母全部转换为大写
print(name.upper())
#lower 将字符串中的字母全部转换为小写
print(name.lower())
#isupper 检查字符串中的字母是否全部为大写
print(name.isupper())
#islower 检查字符串中的字母是否全部为小写
print(name.islower())
#isspace 检查字符串中的字符是否全部为空格
print(name.isspace())
#isalpha 检查字符串中的字符是否全部为字母
print(name.isalpha())
#isdigit 检查字符串中的字符是否全部为数字
print(name.isdigit())
#istitle 检查字符串中的字符是否全部为标题
print(name.istitle())



#join 连接字符串，返回一个字符串
print(",".join(name))
#strip 去除字符串中的空格
print(name.strip(" "))
#lstrip 去除字符串中的左侧空格
print(name.lstrip(" "))
#rstrip 去除字符串中的右侧空格
print(name.rstrip(" "))
#strip 去除字符串中的空格
print(name.strip())

#replace 替换字符串中的某个子字符串
#replace(子字符串,替换字符串,替换次数) 默认替换所有子字符串
print(name.replace("lo","xx"))
print(name.replace("lo","xx",1))
print(name.replace("lo","xx",2))


#列表
#基本格式:
#列表名 = [元素1,元素2,元素3,...]
#注意：所有元素在[]内,元素与元素之间用逗号隔开
#元素之间的数据类型可以各不相同
li = [1,"hello",True,1.1]
print(li,type(li))
#列表也可以进行切片操作
print(li[0:3])
#列表是可迭代对象
for i in li:
    print(i)

#列表操作
#添加元素
# append() extend() insert()
li = ['one', 'two', 'three']
#li.append('four') # 添加一个元素 整体添加
li.extend('four') # 添加多个元素(必须是可迭代对象) 分散添加，将另外一个类型中的元素逐一添加
li.insert(3,'four') #在指定位置添加元素
print(li)

# 修改元素
li[0] = 'five'
print(li)

# 查找元素
# in 或 not in
print('four' in li)
print('four' not in li)
# index()
print(li.index('four'))
print(li.index('four',1))
#print(li.index('four',1,3))
# 统计元素
print(li.count('four'))

# 删除元素
# pop()
li.pop() #python3默认删除最后一个元素
li.pop(0) # 删除指定位置的元素
print(li)
# remove()
li.remove('four') # 删除指定元素的第一个出现的位置
li.clear()
print(li)
# del
# del li[0] # 删除指定位置的元素
# del li
# print(li)

#排序
# sort: 将列表中的元素进行排序，默认从小到大
li = [1,4,5,2,3]
li.sort() # 默认从小到大
li.reverse() # 倒序

#列表推导式
#格式一：[表达式 for 变量 in 可迭代对象]
#注意: in后面的可迭代对象可以是列表，元组，字符串，字典，集合，range()等
li = [1,2,3,4,5,6]
[print(i*5) for i in li] #前面的i是表达式，后面的i是变量
li = []
for i in range(1,6):
    print(i)
    li.append(i)
print(li)
[li.append(i) for i in range(1,6)]
print(li)
#格式二：[表达式 for 变量 in 可迭代对象 if 条件]
li = [i for i in range(1,6) if i%2==0]
print(li)

#列表嵌套 一个列表里面又有一个列表
li = [[1,2,3],[4,5,6],[7,8,9]] # 嵌套列表
print(li[2]) # 访问列表中的元素
print(li[2][1]) # 访问内列表中的元素

#元组 元组中的元素不能修改
#基本格式: 元组名 = (元素1,元素2,元素3,...)
#所有元素包含在()内,元素与元素之间用逗号隔开,不同元素的数据类型可以各不相同
#只有一个元素的时候末尾一定要加上逗号,否则会返回这个元素
t = (1,"hello",True,1.1)
print(t,type(t))

#元组与列表的区别
#1.元组中的元素只能查找，不能新增修改删除
# 可以使用count()、index()、len()与列表的用法相同

#应用场景
# 函数的参数和返回值
# 格式化输出后骂你的()本质上就是一个元组
# 数据不能被修改的时候可以使用元组

#字典
# 格式: 字典名 = {键1:值1,键2:值2,键3:值3,...}
# 键值对形式保存,键和值之间用冒号隔开,每个键值对之间用逗号隔开
dic = {"name":"张三","age":18,"sex":"男"}
print(dic,type(dic))
# 字典中的键具备唯一性,但是值可以重复
dic = {"name":"张三","age":18,"sex":"男","name":"李四"}
print(dic)

#字典的操作
# 查看元素 字典中没有下标，通过键名查找
print(dic["name"]) # 如果键不存在，会报错
print(dic.get("name")) # 如果键不存在，会返回None
print(dic.get("name","不存在")) # 如果键不存在，会返回默认值

# 修改元素
dic["name"] = "赵六" #没有name键名就会新增否则修改
print(dic)

# 删除元素
# del dic
# print(dic)
# dic.pop("name") #键名不存在会报错
# dic.pop()       #报错
# dic.popitem()   #3.7之前删除随机元素，3.7之后删除最后一个元素
# print(dic)
# dic.clear()
# print(dic)
# del dic["name"] #键名不存在会报错
# print(dic)

dic = {"name":"张三","age":18,"sex":"男"}
dic.popitem()
print(dic)
dic.setdefault("name","赵六")
print(dic)
dic.update({"name":"赵六","age":20,"sex":"女"})
print(dic)
dic.fromkeys(["name","age","sex"],0)
print(dic)

#字典求长度
print(len(dic))
# 字典的遍历
print(dic.keys())
for i in dic: #只取出键名
    print(i)
print(dic.values())
for i in dic.values(): #只取出键值
    print(i)
print(dic.items()) #只取出键值对,以元组的形式返回
for i in dic.items(): #只取出键值对
    print(i)
# 字典应用场景
# 1.配置文件
# 2.数据库连接参数
# 使用键值对 存储描述一个物体的相关信息

#集合
# 格式: 集合名 = {元素1,元素2,元素3,...}
s1 = {1,2,3,4,5,6}
print(s1,type(s1))
s1 = {} # 定义空字典
s1 = set() #定义空集合

#集合具有无序性 涉及hash表
s1 = {'a','b','c','d'}
print(s1) #每次运行结果都不一样
s1 = {1,2,3,4,5,6}
print(s1) #每次运行结果都一样

#字符串hash值每次都不一样
print(hash('a'))
print(hash('a'))
print(hash('a'))
#数字hash值每次都一样
print(hash(1))
print(hash(1))
print(hash(1))

# 集合具有唯一性，可以自动去重
s1 = {1,2,3,4,5,6,1,2,3,4,5,6}
print(s1)

#集合添加元素
s1.add(7) # 添加一个元素,如果元素已经存在，则不添加
print(s1)
s1.update([8,9,10]) # 添加多个元素(可迭代对象),如果元素已经存在，则不添加

#集合删除元素
s1.remove(7) # 删除指定元素,如果元素不存在，会报错
s1.discard(8) # 删除指定元素,如果元素不存在，不会报错
s1.pop() # 删除随机元素，默认删除根据hash排序的第一个元素
s1.clear() # 清空集合

#交集 &
s1 = {1,2,3,4,5,6}
s2 = {4,5,6,7,8,9}
print(s1 & s2) #没有共有的部分返回set()空集合

#并集 |
print(s1 | s2) # 所有的都放一起，重复的不放

#类型转换
#int(x) # 将x转换为int类型
n = 1.2
print(int(n))
b = int(n)
print(b,type(b))
#str -> int
a = int('123')
print(a,type(a))
# 如果字符串中有数字和正负号，不可以写在后面
# print(int('10-')) #报错

#用户从控制台输入一个数字，然后打印这个数字
# age = int(input("请输入您的年龄")) #input默认输入的是字符串类型""
# print(type(age))

#float(): 转换为一个小数
print(float(11)) #整型转换为浮点型,会自动添加一位小数
print(float('-11'))
print(float('+11.345'))
# print(float('10-'))

#str(): 将x转换为str类型,任何类型都可以转换成字符串类型""
n = 100
print(type(n)) # <class 'int'>
n2 = str(n)
print(n2,type(n2)) # 100 <class 'str'>
st = str(-1.80)
print(st,type(st)) # float转换成str会取出末位为0的小数点
li = ['1','2','3']
st = str(li) # list转换成str
print(st,type(st))

# eval 用来执行一个字符串表达式,并返回表达式的值
print(10+10)
print('10' + '10')
print(eval('10+10')) # 执行运算，并返回运算值
print(eval("10+ ' 10'")) # 报错，整型和字符串不可以相加

#eval()可以实现list、dict、tuple和str之间的转换
# str -> list
st1 = "[[1,2],[3,4],[5,6]]"
print(type(st1))
li = eval(st1)
print(li,type(li))
# str -> dict
st2 = "{'name':'bingbing','age':18}"
dic = eval(st2)
print(dic,type(dic))

# eval()非常强大,但是不够安全,容易被恶意修改数据,不建议使用

#list() 将可迭代对象转换为一个列表
# 支持转换为list的类型：str,tuple,dict,set
# str -> list
print(list('abcdefg'))
print(list(12345)) #报错

# tuple -> list:
print(list((1,2,3,4,5)))

# dict -> list:
print(list({'name':'bingbing','age':18}))
# 字典转换成列表,会取键名作为列表的值

# set -> list:
print(list({'1','2','3','4','5'}))
# 集合转换成列表,会先去重

# 赋值
li = [1,2,3,4,5]
print(li)
li2 = li # 浅拷贝 将li直接赋值给li2
print('li',li)
print('li2',li2)
li.append('10')
#新增后
print('li',li)
print('li2',li2)
print('li内存地址:',id(li))
print('li2内存地址:',id(li2))
# 内存地址一样,说明是同一个对象

# 浅拷贝(数据半共享) 拷贝速度快，占用空间少
# 会创建新的对象,拷贝第一层数据,嵌套层会指向原来的内存地址
import copy #导入copy模块
li = [1,2,3,[4,5,6]] # 定义一个嵌套列表
li2 = copy.copy(li)  # 浅拷贝
print('li',li)
print('li2',li2)
# 查看内存地址 id()
print('li内存地址:',id(li))
print('li2内存地址:',id(li2))
# 内存地址不一样,说明不是同一个对象
li.append(8)
print('li',li)
print('li2',li2)
# 往嵌套列表添加元素,li2也会添加
li[3].append(7)
print('li',li)
print('li2',li2)
print("li[3]内存地址:",id(li[3]))
print("li2[3]内存地址:",id(li2[3]))

# 深拷贝 (数据完全不共享)
# 外层的对象和内部的元素都拷贝了一遍
import  copy
li = [1,2,3,[4,5,6]]
li2 = copy.deepcopy(li) # 深拷贝
print('li',li,id(li))
print('li2',li2,id(li2))
li.append(8)
print('li',li)
print('li2',li2)
#在嵌套列表中添加元素,li2不会添加
li[3].append(7)
print('li',li)
print('li2',li2)
print("li[3]内存地址:",id(li[3]))
print("li2[3]内存地址:",id(li2[3]))

# 可变对象
# 变量对应的值可以修改，但是内存地址不会发生改变
# 存储空间保存的数据允许被修改，这种数据就是可变类型
# 常见可变类型：list,dict,set
li = [1,2,3,4,5]
print("li的内存地址:",id(li))
li.append(6)
print("li",li)
print("li的内存地址:",id(li))
dic = {'name':'bingbing','age':18}
print("dic的内存地址:",id(dic))
dic['name'] = 'xxxxxx'
print("dic的内存地址:",id(dic))

#不可变对象
# 变量对应的值不可以修改，如果修改就会生成一个新的值从而分配新的内存空间
n = 10 # 整数类型
print("n的内存地址:",id(n))
n = 15
print("n的内存地址:",id(n)) #内存地址不一样，说明生成了新的内存空间

st = 'hello'
print("st的内存地址:",id(st))
st = 'world'
print("st的内存地址:",id(st))

tua = (1,2,3,4,5)
print("tua的内存地址:",id(tua))
tua = (1,2,3,4,6)
print("tua的内存地址:",id(tua)) #内存地址不一样，说明生成了新的内存空间


# 函数
# 定义
# def 函数名(参数):
#     函数体
# 调用函数
# 函数名(参数)
# 函数返回值
# 1.无返回值 返回 None
# 2.一个返回值,就把返回值返回给调用者
# 3.多个返回值,返回一个元组

# 参数
# 1.必备参数
# 格式：def func1(a,b) #写了几个就必须传几个，顺序要一致
# 2.默认参数
# 格式：def func2(a,b=10) #传参的时候可以不传b,默认为10
# 3.可变参数
# 格式：def func3(*args) # *args表示可变参数,可以传多个参数,但是只能传一个元组
# 4.关键字参数
# 格式：def func4(**kwargs) # **kwargs表示关键字参数,可以传多个参数,但是只能传一个字典
def fund(**kwargs):
    print(kwargs)
fund(name='xxxx1',age=18) # 调用参数为 xx=xxx,xx1=xxxxxxxx




# 函数的嵌套
# 1.嵌套调用
def study():
    print('学习')
def course():
    study() # 调用study函数
    print('选课')
course()
# 2.嵌套定义
def study1():
    print("晚上在学习")
    def course2(): #内函数
        print('Python基础') #不要在内存函数中调用外层函数

    course2()
study1()

# 作用域
# 变量生效的范围
# 全局变量:函数外部定义的变量,在整个文件中都是有效的层函数
# 局部变量:函数内部定义的变量,只在函数内部有效
# 在函数内部修噶全局变量的值，可以使用global关键字
a = 140
def test1():
    a = 100
    print(a)
def test2():
    global a # 在局部作用域申明一个全局变量
    a = 200
    print(a)

# nonlocal
# 用来声明外层的局部变量，只能在嵌套函数中使用，在挖补函数先进行声明，内部函数进行 nonlocal声明

# 匿名函数
# 基本语法:  函数名 = lambda 行参:返回值(表达式)
# 调用: 结果 = 函数名(实参)
# lambda 不需要写return返回值，表达式结果就是返回值
add = lambda a,b:a+b #a,b就是匿名函数的行参，a+b就是返回值的表达式
print(add(1,2))
#无参数
funa = lambda :"一桶水果茶"
print(funa())
funa1 = lambda name:name
print(funa1('我是谁'))
#默认参数
func = lambda name,age=18:(name,age)
print(func('bingbing'))
print(func('bingbing',20))
#可变参数
fund = lambda *args:args
print(fund(name='bingbing',age=18))

#lambda结合if判断
a=5
b=8
print("a比b小") if a<b else print("a比b大")
comp = lambda a_,b_: "a比b小" if a_<b_ else "a比b大"
print(comp(a,b))
# lambda只能实现简单的逻辑，如果逻辑复杂不建议使用

#内置函数
#查看所有的内置函数
import  builtins
print(dir(builtins))
# 大写为内置常量名，小写为内置函数名
# abs(): 返回一个数的绝对值
print(abs(-10))
# sum(): 返回一个可迭代对象的和 列表,元組,集合
# min(): 返回一个可迭代对象的最小值
# max(): 返回一个可迭代对象的最大值
print(min(-8,5,key=abs)) #key=abs  先求绝对值

# zip(): 将可迭代对象中的多个元素组合成一个元组
li = [1,2,3]
li2 = [4,5]
print(zip(li,li2)) # 如果元素个数不一样，就会只取最小的个数
print(list(zip(li,li2))) # 把zip结果转换为列表，必须是可迭代对象

# map(): 对可迭代对象中的每个元素应用一个函数，返回一个迭代器
def func(x):
    return x+5

# map(func,iter1) ： iter1 是可迭代对象
mp = map(func,li)
print(mp)
print(list(mp))

# reduce(): 先把对象中的连个元素取出，计算出一个值然后保存着，接下来把这个计算值跟第三个元素进行计算
# 导包
from functools import reduce
# reduce(function,sequence)  # function必须是有两个参数的函数，sequence必须是可迭代对象
li2 = [1,2,3,4,5]
def add(x,y):
    return x+y
res = reduce(add,li2)
print(res)

# 拆包
# 对于函数中的多个返回数据，去掉元组，列表或者字典，直接获取里面的数据的过程
tua = (1,2,3)
print(tua)
print(tua[0])
#方法一
a,b,c = tua #要求元组内的个数与接收的变量个数相同，对象内有多少个数据就需要定义个少个变量接收
#方法一
a,*b = tua #先把单独的参数获取，后面的放在带*好的变量中
print(a,b) #一般在函数调用时使用
def funa(a,b,*args):
    print(a,b)
    print(args,type(args))

#异常
# 抛出异常 raise
# 1.创建一个Exception('xxx')对象，xxx---异常提示异常
# 2.raise抛出这个对象(异常对象)
def funa():
    raise Exception('我是异常')
    print("dddddddddddddd") #执行了raise语法，代码不会继续往下执行

def login():
    pwd = input("请输入密码：")
    if len(pwd) >= 6:
        return "输入密码成功"
    raise Exception("密码长度不够")
try:
    print(login())
except Exception as e:
    print(e)

#模块
# 模块本质上就是一个py文件，里面定义了一些函数
# 1.内置模块 random，time，os，logging 直接导入即可使用
# 2.第三方模块  pip install 模块名
# 3.自定义模块  在当前目录下创建一个py文件，然后import 模块名
#   命名要遵循标识符规定及变量命名规范，不要与内置模块重名

# 导入模块
# 1.导入模块  import 模块名
# 2.调用功能  模块名.功能名
# 从模块中导入指定部分
# from 模块名 import 功能名1,功能名2
# from 模块名 import * #导入模块中所有功能 #不建议过多使用，有时候命名冲突会造成一些错误

# as给模块或功能起别名
# import 模块名 as 别名
# from 模块名 import 功能名 as 别名
# 调用的时候可以直接使用别名

# 内置全局变量 __name__
# 语法 if __name__ == '__main__'
# 用来控制py文件在不同的应用场景执行部同的逻辑
# 1.文件在当前程序执行(即自己执行自己): __name__ == '__main__'
# 2.文件被当作模块被其他文件导入: __name__ == 模块名

#包
# 项目结构中的文件夹/目录
# 包是含有__init__.py文件的目录
# 将有联系的模块放到同一个文件夹下，有效避免模块名称冲突问题，让结构更清晰

# import 导入包时先执行 __init__.py文件的代码 #不建在这个文件中写很多代码
# 导入这个包的其他模块

# __all__ 本质上是一个列表，列表里的元素就是这个包中可以导入的模块

#包的本质上就是一个模块，包又可以包含包

#递归函数
#条件
# 1.必须有一个明确的结束条件 ---递归出口
# 2.每进行更深一层的递归，问题规模相比上次递归都要有所减少
# 3.相邻两次重复之间有紧密的联系


# 斐波那契数列:
# 1,1,2,3,5,8,13....
# 规律： 从第三项开始，每一项都等于前两项之和，即 n = (n-2) + (n-1)
def funa(n):
    if n <= 1:
        return 1
    return funa(n-2) + funa(n-1)
print(funa(6))

#闭包
# 闭包就是函数嵌套函数，并且内部函数引用了外部函数的变量,
# 1.函数嵌套
# 2.内层函数使用外层函数的局部变量
# 3.外层函数返回值是内存函数的函数名
# def outer():     # 外层函数
#     n = 10       # 外层函数的局部变量
#     def inner():  # 内层函数
#         print(n)  # 内层函数使用外层函数的局部变量
#     return inner  # 返内存函数的函数名
# print(outer()) # 返回内层函数的内存地址
# outer()() # 调用内层函数
# ot = outer()
# ot()  #调用内层函数

def outer(m):
    n = 10
    def inner(o):
        print("计算结果:",m+n+0)
    return inner # 返回函数名，而不是inner(),因为inner函数里面参数比较多时或者说受限时，写法不太规范
ot = outer(20) # 调用函数
ot(30)

#通过id 可以判断两个变量是否是同一个值的引用
#内存地址相同，内容相同，内存地址发生变化值也会发生变化

#每次开启内函数都在使用同一份闭包变量
#使用闭包的过程中，一旦外函数被调用一次，返回了内函数的引用，虽然每次调用内函数，会开启一个函数，执行后消亡
#但是闭包变量实际上只有一份，每次开启内函数，都会使用同一份闭包变量

#装饰器
# 1.不修改源程序或函数的代码
# 2.不改变函数或程序调用方法
def test02():
    print("发送信息给彬彬哥")
def test(fn):
    print("开始注册")
    print("登录")
    fn() #调用被装饰的函数

#装饰器
# 含义：装饰器本质就是一个闭包函数，它的好处就是在不修改源代码的情况下，给函数添加一些功能
# 装饰器的原理就是将原有的函数名重新定义为以原函数为参数的闭包
# 1.标准装饰器
def send():                  # 被装饰的函数
    print("发送信息给彬彬哥")
def outer(fn):               #外层函数，fn是形参
    def inner():             #内存函数
        print("开始注册")
        print("登录")
        fn() #调用被装饰的函数
    return inner
ot = outer(send)
ot()

# 2.语法糖
# 格式: @装饰器名称
def outer(fn):
    def inner():
        print("登录")
        fn()
    return inner
#装饰器名称后面不要加上()，前者是引用，后者是调用函数，返回改函数要返回的值
@outer
def send():
    print("发送信息给彬彬哥")

send()

# 被装饰的函数有参数
def outer(fn):
    def inner(name):   #内函数，name是内函数的参数
        print(f"{name}是inner函数中的参数")
        print("开始注册")
        fn(name)
    return inner
@outer
def func(name):
    print("这是被装饰的函数")
func("dxxxx")

def func(*args,**kwargs):
    print(args)
    print(kwargs)

def outer(fn):
    def inner(*args,**kwargs):
        print("登录...")
        fn(args,kwargs)
    return inner
ot = outer(func)
ot('aaa','bbb',age=18)

#多个装饰器
#第一个装饰器
def decol(fn):
    def inner():
        return "哈哈哈" + fn() + "非常优秀"
    return inner
#第二个装饰器
def decol2(fn):
    def inner():
        return "呵呵呵" + fn() + "非常优秀"
    return inner
#@decol1
#@decol2
#def test1():
#    return "我是test1函数"
# test1()
#运行结果--> "哈哈哈 呵呵呵我是test1函数非常优秀 非常优秀"
#离被装饰的函数最近的装饰器先执行


#类的定义
#class 类名:     #类名必须符合标识符规定，同时遵循大写驼峰命名规范
#   代码块
# 查看类属性: 类名.属性名
# 新增类属性: 类名.属性名 = 属性值
# 创建对象
# 实例化对象基本格式： 对象名 = 类名()

# 实例方法
# 由对象调用，至少有一个self参数，执行实例方法的时候，自动将调用该方法的对象赋值给self
# self 表示的是对象本身

# 实例属性
# 格式 self.属性名
class Person:
    name = "dxxxx"  #类属性 类属性既可以用类访问也可以用对象访问
    def introduce(self):
        print(f"我是{self.name},年龄{self.name}")
p = Person()
p.age = 18 #新增实例属性,实例属性只能由对象访问不能由类访问
p.introduce()

# 构造函数 __init__()
# 通常用来做属性初始化或者赋值操作
# 在实例化对象的时候,会被自动调用
class Person:
    def __init__(self,name,age,hight):
        self.name = name
        self.age = age
        self.hight = hight
        print("我是构造函数")
    def play(self):
        print(f"{self.name}在打王者荣耀")
    def introduce(self):
        print(f"我是{self.name},年龄{self.name},身高是{self.hight}")
pe = Person('冰冰',18,170)
pe.play()
pe.introduce()


# 析构函数 __del__()
# 删除对象的时候，解释器会默认调用 __del__()方法
class Person:
    def __init__(self,name,age,hight):
        self.name = name
        self.age = age
        self.hight = hight
        print("我是构造函数")
    def __del__(self):
        print("我是析构函数,被销毁了")
p = Person('冰冰',18,170)
print("这是最后一行代码")
# 结果，del里面是最后执行的代码
del p # 删除对象，解释器会默认调用 __del__()方法
# __del__()主要是表示该程序块或者函数已经全部执行结束


# 面向对象的三大特性: 封装、继承、多态
#封装
#隐藏对象中一些不希望被外部访问的属性和方法
class Person:
    name = "bingbing"
pe = Person()
print(pe.name)
Person.name = "zy"
print(Person.name)

# 隐藏属性
# 在方法名前加上两个下划线(__)，表示该属性是私有的，外部不能访问
class Person:
    name = 'Jame' # 公共属性
    __age = 28    # 私有属性
    def introduce(self):
        print(f"我是{Person.name},年龄{Person.__age}") # 访问私有属性
pe = Person()
print(pe.name)
print(pe.__age) #报错
# 隐藏属性实际上是将属性名字修改为: _类名__属性名  _Person_agePerson
print(pe._Person__age)
pe._Person__age = 15
print(pe._Person__age)
pe.introduce()

#私有属性实
# 1.xxx:普通属性/方法
# 2._xxx:单下划线开头，声明私有属性/方法，如果定义在类中，外部可以使用，子类也可以继承但是在另一个py文件中通过from xxx import * 导入时，无法导入，
# 一般是为了避免与python关键字冲突而采用的命名方法
# 3.__xxx:私有属性/方法 如果定义在类中，无法再外部直接访问，子类不会继承，要访问只能通过间接的方式，另外一个py文件中通过from xxx import *导入的时候也无法导入
# 这种命名一般是python中的魔术方法或属性，都是有特殊含义或者功能的，自己不要轻易定义

class Person:
    name = 'Jame'
    __age = 28   #隐藏属性
    _sex = '男'  #私有属性

pe = Person()
print(pe._sex)
print(pe._Person__age)

class Man:
    def __play(self):       #隐藏方法
        print("我是play方法")
    def funa(self):
        print("平平无奇的方法")
    def _buy(self):         #私有方法
        print("我是buy方法")
ma = Man()
ma.funa()
ma._Man__play()
ma._buy()

# 继承
# 类和类之间转变为父子关系，子类继承父类的属性和方法，子类可以重写父类的方法，也可以添加新的方法
# class 类名(父类名):
# 单继承
class Person:
    def eat(self):
        print("我是吃货")
    def sing(self):
        print("我是唱歌好")
class Girl(Person):
    pass    #占位符，代码里面类下面不写任何东西，会自动跳过，不会报错
class Boy(Person):
    pass
girl = Girl()
girl.eat()
boy = Boy()
boy.eat()

#多态
# 指统一种行为具有不同的表现形式
# 子类、重写
class Animal(object):
    def shout(self):
        print("动物会叫")
class Cat(Animal):
    def shout(self):
        print("猫会喵喵叫")
class Dog(Animal):
    def shout(self):
        print("狗会汪汪叫")
cat = Cat()
cat.shout()
dog = Dog()
dog.shout()

#多态性
class Animal(object):
    def eat(self):
        print("动物会叫")
class Pig(Animal):
    def eat(self):
        print("猪吃猪粮")
class Dog(Animal):
    def eat(self):
        print("狗吃狗粮")
def test(obj):
    obj.eat()
animal = Animal()
pig = Pig()
dog = Dog()
test(animal)
test(pig)
test(dog)
#test函数传入不同的对象，会调用不同对象的方法

#静态方法
# class 类名:
#     使用@staticmethod来进行修饰，静态方法没有self，cls参数的限制
# 静态方法与类无关，可以被转换成函数使用
# 取消不必要的参数传递，有利于减少不必要的内存占用和性能消耗
class Person:
    @staticmethod
    def eat():
        print("我是吃货")
Person.eat()
pe = Person()
pe.eat()

# 类方法
# 使用装饰器@classmethod来标识为类方法，对于类方法，第一个参数必须是类对象，一般是以cls作为第一个参数
# calss 类名
#   @classmethod
#   def 方法名(cls,形参):
#       pass
class Person(object):
    name = "dxxxx"
    @classmethod
    def sleep(cls):
        print("cls:",cls)  #cls代表类对象本身，类本质上就是一个对象
        print("我要睡觉了")
        print("cls.name:",cls.name)
Person.sleep()
#当方法中需要使用到类对象（如访问私有类属性等），定义类方法
#类方法一般是配合类属性使用

#类属性是公共的，所有方法内部都能够访问到，静态方法不需要访问类属性，因为静态方法和类、对象没有关系
#实例属性是私有的，只有实例方法内部才能访问到


# __init__()和__new__()
#__init__():初始化对象
class Test(object):
    def __init__(self):
        print("我是__init__()方法")
    def __new__(cls,*args,**kwargs):
        print("我是__new__()方法")
        print("cls:",cls)
        #对父类方法进行扩展 supper()方法名()
        res = super().__new__(cls)  #方法重写,res里面保存的是实例对象的引用
        # __new__()是静态方法，形参里面有cls，实参就必须传cls
        return res
        # 注意: 重写__new__()一定要return super().__new__(cls),
        # 否则python解释器就得不到分配空间的对象引用,就不会调用__init__()方法

te = Test()
#__new__():object基类提供的内置的静态方法
# 1.内存中为对象分配空间。
# 2.返回对象的引用
# 执行步骤
# 一个对象的实例化过程：首先执行__new__(),如果没有__new__()方法，默认调用object的__new__()，返回一个实例对象,
# 然后再去调用__init__(),对对象进行初始化
#总结： 1.__new__()是创建对象，__init__()是初始化对象
#2.__new__()是返回对象引用,__init__()定义实例属性
#3.__new__()是类级别的方法,__init__()是实例级别的方法

#单例模式
# 可以理解成一个特殊的类，这个类只能存在一个对象
# 可以节省空间，减少了不必要的资源浪费殊的类
# 多线程访问的时候容易引发线程安全问题
# 方式
# 1.通过@classmethond
# 2.通过装饰器实现
# 3.通过重写__new__()方法实现
# 4.通过导入模块实现

class A(object):
    pass
a1 = A()
print(a1)
a2 = A()
print(a2)
# 内存地址发生变化说明是不同的对象块实现
# 实现单例模式 对象的内存地址都是一样的，只有一个对象
# 重写__new__()方法实现单例模式对象
# 设计流程
# 1.定义一个类属性，初始化值为None，用来记录单例对象的引用
# 2.重写__new__()方法
# 3.进行判断，如果类属性是None，把__new__()返回的对象引用保存进去
# 4.返回类属性中记录的对象引用

class Singleton(object):
    # 记录第一个被创建的对象的类型
    obj = None
    def __new__(cls, *args, **kwargs):
        print("这是__new__()方法")
        # 判断类属性是否为None
        if cls.obj is None:
            cls.obj = super().__new__(cls)
        return cls.obj
    def __init__(self):
        print("这是__init__()方法")
s = Singleton()
print("s:",s)
s2 = Singleton()
print("s2:",s2)

#通过导入模块实现单例模式
#模块就是天然的单例模式

#魔法方法&魔法属性
# 1.__doc__:类的描述信息
class Person(object):
    """人类---累的描述信息"""   #只能使用多行注释，单行注释无效
    pass
print(Person.__doc__)
# 2.__module__:当前操作对象所在的模块
# 3.__class__:当前操作对象所在的类
# 4.__str__():对象的描述信息
# 如果类中定义了此方法，那么在打印对象时，默认输出该方法的返回值，也就是打印方法中returns
# 必须是字符串，不能没有返回值
# 5.__del__():析构方法，当对象被销毁时调用，或删除某个对象的时候会被调用
# 6.__call__():使一个实例对象成为一个可调用对象，就像函数那样可以调用
# 可调用对象: 函数、内置函数和类都是可调用对象，凡是可以把一对()应用到某个对象身上都可以称之为可调用对象
# callable(): 判断一个对象是否是可调用对象
def func():
    print("我是一个函数")
func()
print(callable(func)) #True
name = "xxxxx"
#name() #报错
print(callable(name)) #False
class A:
    pass
a = A()
# a() #报错
print(callable(a)) # False

class B:
    def __call__(self,*args,**kwargs):
        print("我是一个可调用对象")
b = B()
b() # 调用一个可调用的实例对象，其实就是在调用它的__call__()方法
print(callable(b)) # True

# 文件操作
# 文件就是存储在某种长期存储设备上的一段数据
# 打开文件 --->读、写、追加 ---> 关闭文件
# 1.open():创建一个file对象，默认是以只读模式打开
# 2.read(n):n表示从文件中读取的数据的长度，没有传入n值就默认一次性读取文件的所有内容
# 3.write(str):将指定内容写入文件
# 4.close():关闭文件
# 属性
# 文件名.name: 返回要打开的文件的文件名，可以包含文件的具体路径
# 文件名.mode: 返回文件的访问模式
# 文件名.closed: 检测文件是否关闭，关闭就返回True，否则返回False
f = open("test.txt")
print(f.name)  #文件名
print(f.mode)  #文件访问模式
print(f.closed) #文件是否关闭
f.close()
# read(n)
# readline(): 一次只读取一行内容，方法执行完，会把文件指针移到下一行，准备再次读取
# readlines(): 一次读取所有行，返回一个列表，列表中的每个元素都是一行内容

# 文件访问模式
file = open("test.txt","r") #只读模式
file.write("....") #报错
file = open("test.txt","w") #重新编辑文件内容，原有内容会被覆盖
file.write("....") #报错
# + 表示可以同时读写某个文件
# r+: 可读写文件，文件不存在就会报错
# w+: 先写再读，文件存在就重新编辑文件，不存在就创建新文件
# a: 追加模式，不存在就创建新文件进行写入，存在则在原有内容的基础上追加新内容。

#文件指针:标记从哪个位置开始读取数据
f = open('test.txt','w+')
f.write("hello python")
print(f.read())
f.close()
# tell() 和 seek()
# tell(): 显示文件内当前位置，即文件指针当前位置
# seek(offset,whence): 移动文件读取指针到指定位置
# offset: 偏移量，表示要移动的字节数
# whence: 起始位置，表示移动字节的参考位置，默认是0,
# 0代表文件开头作为参考位置，
# 1代表当前位置作为参考位置，
# 2代表将文件结尾作为参考位置
# seek(0,0) 会把文件指针移动到文件开头
f = open('test.txt','w+')
f.write("hello python")
pos = f.tell() #14
print(f.read()) #空
f.seek(0,0) #把文件指针移动到文件开头
pos1 = f.tell() #0
print(f.read()) #hello python
f.close()

# with open()
# 代码块执行完，系统会自动调用f.close()方法关闭文件,可以胜利文件关闭步骤
with open("test.txt","r") as f:
    print(f.read())

#编码格式
with open("test.txt","w",encoding="utf-8") as f:
    f.write("你好。我在学校学习")
with open("test.txt",encoding="utf-8") as f:
    print(f.read())

#图片复制
"""
1.读取图片
图片是一个二进制文件，想要写入必须要先拿到
2.写入图片
"""
#读取图片
with open("xxxx.png",'rb') as f:
    img = f.read()
    print(img)
# 将读取到的内容吸入到当前文件中
with open("uuuuu.png",'wb') as f:
    f.write(img)

#目录常用操作
import os
# 1.文件重命名 os.rename(旧名字,新名字)
# 2.删除文件 os.remove(文件名)
# 3.创建文件夹 os.mkdir(文件名)
# 4.删除文件夹 os.rmdir(文件夹名)
# 5.获取当前目录 os.getcwd()
# 6.获取当前目录下的所有文件 os.listdir(路径)


#迭代器
# 1.可迭代对象Iterable
# 遍历(迭代):依次从对象中把一个个元素取出来的过程
# 数据类型: str、list、tuple、dict、set

#可迭代对象的条件
#1.对象实现了 __iter__()方法
#2.__iter__()方法返回了迭代器对象
# for循环工作原理
# 先通过 __iter__()获取可迭代对象的迭代器
# 对获取到的迭代器不断调用__next__()方法来获取下一个值并将其赋值给临时变量i
# isinstance():判断一个对象是否是可迭代对象或者是一个已知的数据类型
from collections.abc import Iterator
# isinstance(o,t) O:对象，t:类型，可以是直接或间接类名，基本类型或者元组
print(isinstance("1234",(int,dict))) #False
print(isinstance("1234",(str))) #True

#迭代器 Iterator
#iter():获取可迭代对象的迭代器
#next():一个一个去取元素，取完元素后会引发一个异常
li = [1,2,3]
li2 = iter(li)
print(li2)
print(next(li2))
print(next(li2))
print(next(li2))
print(next(li2))#报错
#步骤:
# iter()调用对象的__iter__()，并把__iter__()方法的返回结果作为自己的返回值
# next()调用对象的__next__()，一个一个取元素
# 所有元素都取完了,__next__()将引发StopIteration异常

# 可迭代对象iterable和迭代器iterator
# 凡是可以作用于for循环的都属于可迭代对象
# 凡是可以作用于next()的都是迭代器
from collections.abc import Iterable,Iterator
name = "xxxx"
print(isinstance(name,Iterable)) #True
print(isinstance(name,Iterator)) #False
#可迭代对象并不一定是迭代器对象
name = iter(name)
print(isinstance(name,Iterable)) #True
print(isinstance(name,Iterator)) #True
# 迭代器对象一定是可迭代对象
# 总结
# 可迭代对象可以通过iter()转换成迭代器对象
# 如果一个对象拥有__iter__(),是可迭代对象，如果一个对象拥有__next__()和__iter__(),是迭代器对象
# dir()： 查看对象中的属性和方法

# 迭代器协议
# 对象必须提供一个next方法，执行该方法要么就返回迭代中的下一项，要么就引发StopIteration异常，来终止迭代

# 自定义迭代器类
# 两个特性: __iter__()和__next__()
class MyIterator(object):
    def __init__(self):
        self.num = 0
    def __iter__(self):
        return self   # 当前迭代器类的实例对象""
    def __next__(self):
        # 判断是否大于10
        if self.num > 10:
            raise StopIteration("终止迭代，数据已经被去完了")
        self.num += 1
        return self.num

te = MyIterator()
print(te)
print(next(te)) #1

# 生成器
# python中一边循环一边计算的机制，叫做生成器
# 1.生成器表达式
# 列表推导式
li = [i*5 for i in range(5)]
gen = (i*5 for i in range(5)) #列表推导式的[]改成()就成了生成器表达式
print(li)
print(gen)
print(next(gen)) #0
print(next(gen)) #1
print(next(gen)) #2
# 生成器函数
# python中，使用了 yield关键字的函数就称之为生成器函数
# yield的作用:
# 1.类似return，将指定值或多个值返回给调用者
# 2.yield语句一次返回一个结果，在每个结果中间，挂起函数，执行next(),再重新从挂起点继续往下执行
#  使函数中断，并保存中断的状态
def test():
    li = []
    li.append('a')
    print("li:",li)
test() # li:['a']
test() # li:['a','a']
#生成器函数
def gen():
    print("开始了")
    yield "a" #返回值a，暂停函数在此处挂起，下一次再从此处恢复运行
    yield "b"
    yield "c"
gen_01 = gen()
print(next(gen_01)) # a
print(next(gen_01)) # b
print(next(gen_01)) # c

def gen2(n):
    a = 0
    while a < n:
        li.append(a)
        yield a
        a += 1
    print("li:",li)
print(gen2(5))
for i in gen2(5):
    print(i)

# 使用了yield关键字就是生成器函数
def test_a():
    yield 1
    yield 2
    yield 3
print(test_a())
ta = test_a()
print(next(ta)) # 调用的是生成器对象，从对象中取值
print(next(ta)) # 2
print(next(ta)) # 3
print(next(test_a())) # 加括号是调用函数
print(next(test_a())) # 1

# 可迭代对象：指实现了python迭代协议，可以通过for..in..循环遍历的对象，比如list,dict,str...、迭代器、生成器函数
# 迭代器: 可以记住自己遍历位置的对象，直观体现就是可以使用next()方函数返回值，迭代器只能往前，不能往后，
#        当遍历完毕之后，next()会抛出异常
# 生成器: 是特殊的迭代器，需要注意迭代器并不一定是生成器，它是python提供的通过简便的方法写出迭代器的一种手段
# 包含关系: 可迭代对象 > 迭代器 > 生成器

from threading import Thread # 导入线程模块
# 线程
# 是cpu调度的基本单位，每个进程至少都会有一个线程，这个线程通常就是我们所说的主线程
# 一个进程默认有一个线程，进程里面可以创建多个线程，线程是依附在进程里面的，没有进程就没有线程

# Thread 线程类参数
# target: 线程要执行的函数名或任务名
# args: 线程要执行的函数的参数 元组形式
# kwargs: 线程要执行的函数的参数 字典形式

def sing(name):
    print(f"{name}在唱歌")
    time.sleep(2)
    print("唱完歌了")
def dance(name2):
    print(f"{name2}在跳舞")
    time.sleep(2)
    print("跳舞完了")
t1 = threading.Thread(target=sing,args=("小明",))
t2 = threading.Thread(target=dance,args=("小明",))
# 守护线程，必选放在start()前面:主线程执行结束，子线程也会跟着结束
t1.setDaemon(True)
t2.setDaemon(True)
# 开启线程
t1.start()
t2.start()
# 阻塞主线程join()：暂停的作用，等子线程执行结束后，主线程才会继续执行，必须放在start()后面
t1.join()
t2.join()
# 获取线程名
print(t1.getName())
# 修改线程名
t1.setName("线程1")
print("完美谢幕，本次表演结束!!")

def task():
    time.sleep(1)
    print("当前线程是:",threading.current_thread().name) #显示当前线程对象名

# 线程之间共享资源(全局变量)


# 写入数据
li = []
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(1)
    print("写入的数据是:",li)
#读取数据
def rdata():
    print("读取的数据是:",li)
#创建子线程
t1 = Thread(target=wdata)
t2 = Thread(target=rdata)
#开启子线程
t1.start()
#阻塞线程
t1.join() #等待t1线程执行完毕
t2.start()
t2.join()

# 资源竞争
a = 0
b = 10000000
# 循环一次就给全局变量a+1
def add():
    for i in range(b):
        global a
        a += 1
    print("第一次:",a)
def add2():
    for i in range(b):
        global a
        a += 1
    print("第二次:",a)
add()       #第一次: 1000000
add2()      #第二次: 2000000
first = Thread(target=add)
second = Thread(target=add2)
first.start() # 第一 128988
#first.join()
second.start() # 第二 139049

# 线程同步
# 1.线程阻塞 join
# 2.互斥锁   acquire()上锁  release()释放锁
# 创建互斥锁  注意:互斥锁也是多个线程去抢，影响代码执行效率
lock = threading.Lock()
lock.acquire()
lock.release()

# 进程
# 操作系统进行资源分配和调度的基本单位，是操作系统结构的基础
# 一个正在运行的程序或者软件就是一个进程
# 程序跑起来就成了进程
# 注意：进程里面可以创建多个线程，多进程也可以完成多任务
# 进程状态
# 1.就绪状态:运行的条件都已经满足，正在等待cpu执行
# 2.运行状态:正在被cpu执行
# 3.等待(阻塞)状态: 等待某些条件满足，如一个程序sleep了，此时就处于等待状态
print("我是彬彬") # 程序开始，处于执行状态
sex = input("请输入你的性别:") # 光标闪动，等待用户输入，处于等待状态
print("sex:",sex)  # 执行状态
time.sleep(1)   # 延时1秒，处于等待(阻塞)状态

from multiprocessing import Process
# 进程语法结构
# multiprocessing模块提供了Process类代表进程对象
# 1.Process 类参数
# target:执行的目标任务名，即子进程要执行的任务
# args:以元组的形式传参
# kwargs:以字典的形式传参
# 2.常用的方法
# start():启动进程，调用target方法
# is_alive(): 判断进程是否还活着，存活返回True，否则返回False
# join():主进程等待子进程执行结束
# 3.常用的属性
# name: 当前进程的别名。默认值是Process-N，N是进程编号
# pid: 当前进程的进程编号

def sing():
    spid = os.getpid() # 获取当前进程的进程编号
    ppid = os.getppid() # 获取当前进程的父进程的进程编号
    print("唱歌")
def dance():
    print("跳舞")
# 创建子进程
# 修改子进程名第一种方法
p1 = Process(target=sing,name="子进程一")
p2 = Process(target=dance)
p1.start()
p2.start()
# 修改子进程名第二种方法
p1.name = "xxxx"
print(p1.name)
print(p2.name)
print(p1.pid)
print(p2.pid)

def eat(name):
    print(f"{name}在干饭")
def sleep(name):
    print(f"{name}在睡觉")
p1 = Process(target=eat,args=("小明",))
p2 = Process(target=sleep,args=("小红",))
p1.start()
p1.join() # 主进程处于等待的状态，p1是运行状态
p2.start()
p2.join()
print("p1存活状态:",p1.is_alive())
print("p2存活状态:",p2.is_alive())
# 写在主进程中判断存活状态的时候需要加入join阻塞一下

# 进程间不共享全局变量
li = []
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(1)
    print("写入的数据是:",li)
def rdata():
    print("读取的数据是:",li)

# 防止别人导入文件的时候执行main里面的方法
# 防止windows系统递归创建子进程
if __name__ == '__main__':
    p1 = Process(target=wdata)
    p2 = Process(target=rdata)
    p1.start()
    p1.join()
    p2.start()
# 读取一直为空，因为不共享全局变量

from queue import Queue
# 进程间通信 Queue队列
# q.put(): 放入数据
# q.get(): 取出数据
# q.empty(): 判断队列是否为空，为空返回True，否则返回False
# q.qsize(): 返回当前队列包含的消息数量
# q.full(): 判断队列是否已满，已满返回True，否则返回False
# 初始化一个队列队形
q = Queue(3) # 最多可以接收三条消息，没写或者是负值代表没有上线
q.put("xxxx1")
q.put("xxxx2")
q.put("xxxx3")
print(q.qsize())
print(q.get())
print(q.full())

li = ["张三","李四","王五","赵六"]
def wdata(q1):
    for i in range(5):
        print(f"{i}已经被放入")
        q1.put(i)
        time.sleep(0.2)
    print("写如的数据是:",li)
def rdata(q2):
    while True:
        if q2.empty():
            break
        else:
            print(f"{q2.get()}已经被取出")
    print("读取的数据是:",li)
q = Queue()
p1 = Process(target=wdata,args=(q,))
p2 = Process(target=rdata,args=(q,))
p1.start()
p1.join()  # 等待队列中的数据放入完成之后再执行
p2.start()

#协程
# greenlets
# 协程: 单线程下的开发，又称为微线程
# 注意: 线程和进程的操作是由程序触发系统接口，最后的执行者是系统，协程的操作则是程序员
# 1.简单实现协程
def task1():
    yield 'a'
    yield '哈哈哈'
def task2():
    yield 'b'
    yield '嘿嘿嘿'
t1 = task1()
print(t1) #生成器对象
t2 = task2()
print(next(t1)) # a
print(next(t2)) # b
print(next(t1)) # 哈哈哈
print(next(t2)) # 嘿嘿嘿

def task3():
    while True:
        yield "哈哈哈"
def task4():
    while True:
        yield "嘿嘿嘿"
t3 = task3()
t4 = task4()
while True:
    print(next(t3))
    print(next(t4))

#协程的应用场景
#1.如果一个线程里面IO操作比较多的时候，可以用携程
#  Input/Output
#  常见的IO操作：文件操作、网络请求
#2.适合高并发处理

# pip install greenlet
# 是一个由C语言实现的协程模块，通过设置switch()来实现任意函数之间的切换
# 注意：greenlet属于手动切换，当遇到IO操作，程序会阻塞，而不能进行自动切换
# 通过greenlet实现任务的切换
from greenlet import greenlet
def sing():
    print("在唱歌")
    g2.switch()
    print("唱完歌了")
def dance():
    print("在跳舞")
    print("跳完舞了")
    g1.switch()
# sing（）
# dance()
#创建协程对象greenlet(任务名)
g1 = greenlet(sing)
g2 = greenlet(dance)
#g1.switch()    #切换到g1中去运行
g2.switch()     #切换到g2中去运行

# gevent 遇到IO操作时，会进行自动切换，属于主动式切换
# 注意: 文件命名不要和第三方或内置模块重名
# gevent.spawn() ：创建携程对象
# gevent.sleep() ：延时操作
# gevent.join()：阻塞，等待某个协程执行结束
# gevent.joinall()：等待所有协程对象都执行结束再退出，参数是一个携程对象列表
# import gevent
def sing():
    print("在唱歌")
    #time.sleep(2)
    #gevent.sleep(2)
    print("唱完歌了")
def dance():
    print("在跳舞")
    #time.sleep(3)
    #gevent.sleep(3)
    print("跳完舞了")
# 创建协程对象
g1 = gevent.spawn(sing)
g2 = gevent.spawn(dance)
# 阻塞，等待协程执行结束
g1.join()
g2.join()
# joinall()
def sing(name):
    for i in range(3):
        # gevent.sleep(1)
        print(f"{name}在唱歌，被送走的第{i}次")
gevent.joinall([
    gevent.spawn(sing,"小明"),
    gevent.spawn(sing,"小红"),
    gevent.spawn(sing,"小花")
])
# joinall():等待所有的协程都执行结束再退出
# monket 补丁:拥有在模块运行时替换的功能
# from gevent import monkey
# monkey.patch_all() #将用到的time.sleep()代码替换成gevent里面自己实现耗时操作的gevent.sleep()代码
#  注意: monkey.patch_all() 必须放在被打补丁者的前面
def sing(name):
    for i in range(3):
        time.sleep(1)
        print(f"{name}在唱歌，被送走的第{i}次")
gevent.joinall([
    gevent.spawn(sing,"小明"),
    gevent.spawn(sing,"小红"),
    gevent.spawn(sing,"小花")
])

# 总结果
# 线程是CPU调度的基本单位，进程是资源分配的基本单位
# 进程 切换需要资源最大，效率最低
# 线程 一般，效率一般
# 协程 最小，效率高
# 多线程适合IO密集型操作(文件操作，爬虫)，多进程适合CPU密集型操作(科学计算，游戏，视频，音频)
# 进程、线程、协程都是可以完成多任务的，可以根据自己实际开发的需要选择使用


#正则表达式
# 记录文本规则的代码
import re
#字符串处理工具
#  语法比较复杂，可读性差
#  通用性很强，适用多种编程语言
#步骤
# 1.导入re模块
# 2.使用match方法进行匹配操作
#   re.match()能匹配出以xxx开头的字符串
#   如果起始位置没有匹配成功，返回None
# re.match(pattern,string)
# pattern 匹配的正则表达式
# string 要匹配的字符串
res = res.match("冰冰","冰水xxx") #匹配的是整体
print(res.group())
#注意: match是从开始位置匹配，匹配不到就没有，且匹配的是表达式整体

# 匹配单个字符
# 1. .匹配任意一个字符，除\n以外  --常用
# res = re.match('.e','hello')
# print(res.group())
# 2. []匹配[]中列举的字符     --常用
# res = re.match('[he]','hello')
# res = re.match('[1-5]','5423')
# 匹配0-9第一种写法
# res = re.match('[0123456789]','9325')
# 匹配0-9第二种写法
# res = re.match('[0-9]','9325')
# res = re.match('[a-zA-Z][a-zA-Z]+','Hello')
# print(res.group())
# 3. \d 匹配数字0-9
# res = re.match('.\d\d\d.','s243s')
# print(res.group())
# 4. \D 匹配非数字                --常用
# res = re.match('\D','<243s')  #只要不是数都能匹配置
# print(res.group())
# 5. \s 匹配空白，即 空格和tab键
# res = re.match('\s\sh','  hello')
# print(res.group())      #\s\s ==> 1个tab
# 6. \S 匹配非空白，即 非空格和tab键
# res = re.match('\S','|aha')
# print(res.group())
# 7. \w 匹配单词字符，即a-z,A-Z,0-9,_,汉字        --常用
# res = re.match('\w','bingbing')
# print(res.group())
# 8. \W 匹配非单词字符
# res = re.match('\W','.bingbing')
# print(res.group())

# 匹配多个字符
# 1. * 匹配前一个字符出现0次或者无限次，即可有可无   --常用
res = re.match('\d*','bingbing')
print(res.group())
# 2. + 匹配前一个字符出现1次或者无限次，即至少一次   --常用
res = re.match('\d+','地主家的傻儿子')
print(res.group())
# 3. ? 匹配前一个字符出现0次或者1次，即0次或者1次   --常用
res = re.match('\s?','12hello')
print(res.group())
# 4. {m}匹配前一个字符出现m次
res = re.match('\w{1}','python')
print(res.group())
# 5. {m,n}匹配前一个字符出现m次或者n次
# 注意： 必须符合m<n的条件
res = re.match('\w{3,9}','python')
print(res.group())

#匹配开头和结尾
# 1. ^ 表示以...开头;表示对...取反
res = re.match('^py','python')
print(res.group())
# 注意L ^在[]中表示不匹配
res = re.match('[^py]','123python') #[^py]表示匹配除了p、y之外的字符
print(res.group())
# 2. $ 匹配以...结尾
res = re.match('.{7}\w$','bingbing')
print(res.group())

# 匹配分组
import re
#1.  |匹配左右任意一个表达式          ---常用
res = re.match("abcd|deg",'def')
print(res.group())
#2.  (ab)将括号中字符作为一个分组      ---常用
res = re.match("\w*@(163|qq|126).com","123@163.com")
print(res.group())
#3. \num 匹配分组num匹配到的字符串     ---经常在匹配标签时被使用
res = re.match(r"<(\w*)>\w*</\1>",'<html>login</html>')
res = re.match(r"<(\w*)><(\w*)>.*</\2></\1>",'<html><body>login</body></html>')
print(res.group())
# 注意： 从外到内排序，编号从1开始

# 4.(?P<name>) 分组起别名
# 5.(?P=name) 引用别名为name分组匹配到的字符串
# res = re.match(r"<(?P<L1>\w*)><(?P<L2>\w*)>.*</(?P=L2)></(?P=L1)>",'<html><body>login</body></html>')
# print(res.group())

# 匹配网址   前缀一般是 www,后缀：.com、 .cn、org
li = ['www.baidu.com','www.qq.com','www.163.com','www.google.com']
res = re.match(r"www(\.)\w*\1(com|cn|org)",'www.baidu.com')
print(res.group())

#高级用法
# 1. search():扫描整个字符串并返回第一个成功的匹配的对象，如果匹配失败，返回None
res = re.search('\d','pythong')
print(res.group())
# 2. findall(): 从头到尾匹配，找到所有匹配的字符串，返回一个列表
res = re.findall('\d','py12345678thong')
print(res)
print(type(res))

#总结:
# match() 从头开始匹配，匹配成功返回match对象，通过group进行提取，匹配失败返回None，只匹配一次
# search() 从头到尾匹配，匹配成功返回第一个成功匹配的对象，通过group进行提取，匹配失败返回None，只匹配一次
# findall() 从头到尾匹配，匹配成功返回一个列表，匹配失败返回空列表，匹配多次

# sub()
# re.sub(pattern,repl,string,count)
# pattern 正则表达式(代表需要被替换的，也就是字符串里面的旧内容)
# repl 新内容
# string 字符串
# count 替换的次数，默认为0，表示替换所有匹配的字符串
res = re.sub('bing','b','hellobingbing',1)
print(res)
res = re.sub('\d','2','这是这个月的第30天',1)
print(res)

# split(pattern,string,maxsplit)
# patrern 正则表达式
# string 字符串
# maxsplit 最多分割次数，默认为0，表示分割所有匹配的字符串
res = re.split(',','hello,python',2)

# 贪婪与非贪婪
# 1.贪婪匹配(默认):在满足匹配时，匹配尽可能长的字符串
res = re.match("em*",'emmmm.....')
print(res.group())
# 2.非贪婪匹配:在满足匹配时，匹配尽可能短的字符串,使用？来表示非贪婪匹配
res = re.match("em+?",'emmmm.....')
res = re.match("e{1,5}",'emmmm.....') #匹配1-5个e
res = re.match("e{1,5}?",'emmmm.....') #匹配1个
print(res.group())

# 原生字符
print(r"sixs\tar") #r取消转义
res = re.match("\\\\",'\game') # 匹配一个\
res = re.match(r"\\\\",'\game') # 匹配二个\\


# os 用于和操作系统进行交互
# 1. os.name    #指示正在使用的工作平台(操作系统类型)
print(os.name)
# 对于window返回nt，对于Linux返回posix
# 2. os.getenv(环境变量名称) #读取环境变量
print(os.getenv('PATH'))
# 3. os.path.split() # 把目录名和文件名分离，以元组的形式接收，第一个是元素是目录路径，第二个是文件名
o = os.path.split(r'D:\Python\ccc.txt')
print(o[0])
print(o[1])
# 4. os.path.dirname() # 显示split分割的第一个元素，即目录
# 5. os.path.basename() # 显示split分割的第二个元素，即文件名
print(os.path.dirname(r'D:\Python\ccc.txt'))
print(os.path.basename(r'D:\Python\ccc.txt'))
#print(os.path.basename(r"D:\xxx\")) #以/结尾,返回空，以\结尾会报错
# 6. os.path.exists() # 判断文件或目录是否存在，存在返回True，不存在返回False
# 7. os.path.isfile() # 判断是否存在文件
# 8. os.path.isdir() # 判断是否存在目录
# 9. os.path.abspath() # 获取当前路径下的绝对路径
# 10. os.path.isabs() # 判断是否为绝对路径

# sys 复杂程序跟Python解析器的交互
# 1. sys.getdefaultencoding() # 获取默认编码
print(sys.getdefaultencoding())
# 2. sys.path: 获取环境变量的路径，跟解释器相关
print(sys.path) # 以列表的形式返回,第一项为当前所在的工作目录""
# 3. sys.platform # 获取当前python解释器的平台名称
print(sys.platform)
# 4. sys.version # 获取python解释器的版本信息
print(sys.version)

# time模块
# 1.时间戳(timestamp)
# 2.格式化的时间字符串(format time)
# 3.时间元组(strut_time)
#1.time.sleep() # 延时操作，以秒为单位
#2.time.time() # 获取当前时间戳，以秒计算，从1970年1月1日0时0分0秒开始到现在的时间差
print(time.time()) # 返回的是浮点型
#3.time.localtime() # 将一个时间戳转换为当前时区的struct_time，九个元素
print(type(time.localtime()))
print(time.localtime())
t = time.localtime()
print(t.tm_year)
#4.time.asctime() # 获取系统当前时间，把struct_time转换成固定的字符串表达式
print(time.asctime())
t = time.localtime()
print(t)
print(time.asctime(t))
#5.time.ctime() #获取系统当前时间，把时间戳转换成固定的字符串表达式
#print(time.ctime())
t = time.time()
print(time.ctime(t))
#6.time.strftime(格式化字符串,struct_time) # 把struct_time转换成指定的字符串表达式
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
#7.time.strptime(时间字符串,格式化字符串) # 把字符串表达式转换成struct_time
print(time.strptime('2019-01-01 00:00:00','%Y-%m-%d %H:%M:%S'))

# logging
# 记录日志信息
# 级别除达式(从高到低)
# CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
logging.debug("我是debug")
logging.info("我是info")
logging.warning("我是warning")
logging.error("我是error")
logging.critical("我是critical")
# logging 默认的level是WARNING，所以默认不会输出debug和info级别的日志
# logging.basicConfig(level=logging.DEBUG) #配置root logger的参数
# 1.filename:日志文件名，默认为None，表示不写文件，默认为当前目录下的log.txt
# logging.basicConfig(filename='log.log')
# 2.filemode:文件打开模式，默认为a，表示追加，w表示覆盖，默认为a
# logging.basicConfig(filename='log.log',filemode='w')
# 3.level 指定日志显示级别，默认是警告warning
# logging.basicConfig(level=logging.NOTSET)
# 4.format:日志显示格式，默认为%(asctime)s - %(name)s - %(levelname)s - %(message)s
# logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# random 模块
# 用于实现各种分布的伪随机数生成器，可以根据不同的实数分布来随机生成值
# 1. random.random()  # 产生大于0且小于1之间的小数
# 2. random.uniform()  #产生指定范围的随机小数
# print(random.uniform(1,3 ))
# 3. random.randint() # 产生指定范围的随机整数,包括开头和结尾
# print(random.randint(1,3))
# 4. random.randrange(start,stop,[step]) # 产生star，stop范围内的随机整数，不包括stop，step为步长，默认为1
# print(random.randrange(2,5,3))


# 网络请求
# 请求过程:
#客户端、值web浏览器向服务器发送请求

# 请求分为四部分:
# 1.请求网址  -- request url
# 2.请求方法   -- request methods
# 3.请求头     -- requst header
# 4.请求体     -- request body

# 爬虫分类
# 通用爬虫、聚焦爬虫
# 功能性爬虫、数据增量爬虫

# 爬虫基本流程
# 1.确认目标  url:www.baidu.com
# 2.发送请求: 发送网络请求，获取到特定的服务端给你的响应
# 3.提取数据: 从响应中提取特定的数据   jsonpath/xpath/re
# 4.保存数据: 本地(html,json,txt)、数据库

#获取到的响应中，有可能会提取到还需要继续发送请求的url，可以拿着解析到的url继续发送请求

# 5.robots 协议
# robots协议并不是一个规范，只是约定俗成

# http协议和https协议
# http协议：规定了服务器和客户端互相通信的规则
# http协议： 超文本传输协议，默认80端口
# 超文本： 不仅仅限于文本，还包括图片，视频，音频等
# 传输协议：指使用共用约定的固定格式来传递转换成字符串的超文本内容

# https协议：http+ssl(安全套接字层)，默认443端口
# 带有安全套接字层的超文本传输协议
# ssl对传输的内容进行加密

# https比http更安全，但是性能更低

# http请求/响应的步骤:
# 1.客户端连接到web服务器
# 2.发送http请求
# 3.服务器接受请求返回响应
# 4.释放连接tcp连接
# 5.客户端解析html内容




# 请求头
# 请求方式: get 和 post
# get： 获取资源
# post： 提交资源
# User-Agent: 模拟正常用户
# cookie: 登录保持
# referer: 当前这一次请求是 由哪个请求过来的
# 1.User-Agent: 浏览器信息
# 2.Accept: 浏览器可以接收的内容类型
# 3.Accept-Language: 浏览器可接受的语言
# 4.Accept-Encoding: 浏览器可接受的编码格式
# 5.Accept-Charset: 浏览器可接受的字符集
# 6.Connection: 浏览器是否断开连接
# 7.Cookie: 浏览器的cookie
# 8.Referer: 请求来源
# 9.Host: 请求的服务器地址
# 10.Origin: 请求的来源地址

# requests 模块
url = 'https://www.baidu.com'
response = requests.get(url)
print(response) # <Response [200]>

#打印响应内容
print(response.text) #响应内容有乱码，requests模块会自动寻求一种解码方式解码
print(response.content.decode())

# 保存图片
url = "https://dddd.pic.com"
res = requests.get(url)
res.encoding = "utf-8"
print(res.text) # 不乱码
# 响应url
print(res.url)
# 响应对应的请求头
print(res.request.headers)
# 响应头
print(res.headers)
print(res.apparent_encoding)
# 文本为w，图片为wb
with open('1.jpg',wb) as f:
    f.write(res.content)

with open('1.html',w,encoding='utf-8') as f:
    f.write(res.content.decode())

# response.text 和 response.content的区别:
# text：str类型，requests模块自动根据http头部对响应的编码作出有根据的推测
# content: bytes类型，可以通过decode()解码

# 用户代理
# 请求头中user-agent 字段必不可少，表示客户端的操作系统以及浏览器的信息
url = "https://www.baidu.com"
headers = {
    "User-Agent":""
}
# 发送请求头
# heads参数接受字典形式的请求头，请求头字段名key。值为value
res = requests.get(url,headers=headers)
print(res.content.decode())
print(len(res.content.decode()))

# 添加user-agent的目的是为了让服务器认为是浏览器在发送请求，而不是爬虫程序在发送请求

# user-agent池  --防止反爬
UAlist = [
    '',
    '',
]
#随机选取user-agent
print(random.choice(UAlist))
# 使用fake_useragent   可能会出现异常
# from fake_useragent import UserAgent
# print(UserAgent().random)

# 浏览器发送请求的原理
# 1.构建请求
# 2.查找缓存
# 3.准备ip地址和端口
# 4.等待tcp队列
# 5.建立tcp连接
# 6.发送http请求

# Url 传参
# 字符串被当作url提交时会被自动进行url编码处理

# 输入 ---学习
# 发哦那个请求的时候 -- %E5%AD%A0    密文

from urllib.parse import quote,unquote
# quote()  -- 明文转密文
# unquote() -- 密文转明文    传入参数类型:%xx%xx%xx%xx%xx%
print(quote('学习'))
print(unquote('%E5%AD%A0'))

# 发送带参数的请求
# import requests
url = "https://www.baidu.com/s?wd=%E5%AD%A0"
res = requests.get(url,headers=headers)
print(res.content.decode())

# 通过params携带参数字典
# 1.构建请求参数字典
# 2.发送请求的时候带上请求参数字典

url = 'https://www.baidu.com/s?'
headers = {
    "User-Agent":""
}
# 构建请求参数字典
name = input("请输入关键字:")
kw = {'wd':name}
res2 = requests.get(url,params=kw,headers=headers)

# 1.获取单张图片
url = 'https://www.ddddd.com'
headers = {
    "User-Agent":""
}
res = requests.get(url,headers=headers)
with open('1.jpg','wb') as f:
    f.write(res.content)

# 2.获取单首歌曲
url = 'https://www.ddddd.com'
headers = {
    "User-Agent":""
}
res = requests.get(url,headers=headers)
with open('1.mp3','wb') as f:
    f.write(res.content)

# 3.获取单个mv
url = 'https://www.ddddd.com'
headers = {
    "User-Agent":""
}
res = requests.get(url,headers=headers)
with open('1.mp4','wb') as f:
    f.write(res.content)

# 4.贴吧单页获取
url = 'https://www.ddddd.com'
headers = {
    "User-Agent":""
}
res = requests.get(url,headers=headers)
print(res.text)
with open('1.html','wb') as f:
    f.write(res.content)

# 5.翻页操作
# url = 'https://www.ddddd.com?pn=0'
# url = 'https://www.ddddd.com?pn=50'
# url = 'https://www.ddddd.com?pn=100'

url = 'https://www.ddddd.com'
headers = {
    "User-Agent":""
}
word = input("请输入关键字:")
page = int(input("请输入页数:"))
for i in range(page):
    pn = i*50
    kw = {'wd':word,'pn':pn}
    res = requests.get(url,params=kw,headers=headers)
    with open(f'{pn}.html','wb') as f:
        f.write(res.content)

# 6.改写为面向对象
# class Tieba:
#     def __init__(self):
#         self.url = 'https://www.ddddd.com'
#         self.headers = {
#             "User-Agent":""
#         }
#     def send(self,params):
#         res = requests.get(self.url,params=params,headers=self.headers)
#         return res.text
#
#     def save(self,page,con):
#         with open(f'{page}.html','w',encoding='utf-8') as f:
#             f.write(con)
#
#     def run(self):
#         word = input("请输入关键字:")
#         pages = int(input("请输入页数:"))
#
#         for page in range(pages):
#             pn = page*50
#             param = {'wd':word,'pn':pn}
#             con = self.send(kw)
#             self.save(pn,con)

# post 请求
# 登录注册，转送大文本内容
# requsets.post(url,data)
# data参数接收一个字典

# cookie 模拟登录
url = 'https://www.ddddd.com'
headers = {
    "User-Agent":"",
    'Cookie': 'xxx'
}
res = requests.get(url,headers=headers)
print(res.text)

# post --金山翻译
url = 'https://fanyi.youdao.com/translate'
headers = {
    "User-Agent":""
}
# 构建data参数字典
post_data = {
    'from':'zh',
    'to':'en',
    'q':'中文'
}
res = requests.post(url,data=post_data,headers=headers)
print(res.text)

# 解析数据
# 将json数据转为python字典
dic = json.loads(res.text)
print(type(dic))
print(dic['out'])

# session -- 自动处理cookie
# request.session() #实例化session对象
# response = session.post(url,data=data)
# session.get(url.text)

# 1.对访问登录后才能访问的页面去进行抓包
# 2.确定登录请求的url地址，请求方法和所需的参数
# 3.确定登录才能访问的页面url和请求方法
# 4.利用requests.session()完成代码


# cookie池
# user-agent池: 短时间内多次发出请求，尽量每次的请求都用不同的用户代理
# cookie池： 每一个cookie就代表一个账号

# cookie 有有效期
# session 不用担心有效期的问题

# cookie跟session的区别:
# 1.cookie数据放在客户端的浏览器上，session数据放在服务器上
# 2.cookie不是很安全，别人可以分析存放在本地的cooker并进行cookie欺骗，考虑到安全应单使用session
# 3.session 会在一定时间内保存在服务器上，考虑到减轻服务器性能方面，应当使用cookie
# 4.可以考虑将登录信息等重要信息存放在session，其他信息如果需要保留，可以放在cookie中

# 代理
# 代理ip是一个ip，指向的是一个代理服务器
# 代理服务器能够帮我们向目标服务器转发请求

# ip地址:精确的定位

# 正向代理
# 给客户端做代理，让服务器不知道客户端的真实身份
# 保护自己的ip地址不会被封，要封也是封代理ip
# 反向代理
# 给赋权做代理，让浏览器不知道服务器的真实地址
# 正向代理保护客户端，反向代理保护服务端

# 代理分三类:
# 1.透明代理: 服务器知道我们使用了代理ip，也知道真实ip
# 2.匿名代理: 服务器能够检测到使用了代理ip，但是不知道真实ip
# 3.高匿代理: 服务器不知道使用了代理ip，也不知道真实ip

#proxies 代理参数
proxies = {
    "http": "",
    "https": ""
}
res = requests.get(url,headers=headers,proxies=proxies)
# 代理ip无效，会自动使用本机的真实ip

# 按装订区域中的绿色按钮以运行脚本。
if __name__ == '__main__':
    print_hi('我是谁呀')

# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
